use neon::prelude::*;
use neon::types::buffer::TypedArray;
use std::string::String;
use std::ptr;
use std::slice;
use std::io::Cursor;
use winapi::um::winuser::{OpenClipboard, CloseClipboard, GetClipboardData, SetClipboardData, EmptyClipboard, CF_UNICODETEXT, CF_BITMAP, CF_DIBV5};
use winapi::um::winbase::{GlobalLock, GlobalUnlock, GlobalAlloc, GMEM_MOVEABLE, GlobalSize};
use winapi::shared::minwindef::HGLOBAL;
use image::ImageFormat;

fn read_clipboard(mut cx: FunctionContext) -> JsResult<JsObject> {
    unsafe {
        if OpenClipboard(ptr::null_mut()) == 0 {
            return cx.throw_error("Failed to open clipboard");
        }

        let result = cx.empty_object();
        
        // Try to read unicode text
        let unicode_data = GetClipboardData(CF_UNICODETEXT);
        if !unicode_data.is_null() {
            let hmem = unicode_data as HGLOBAL;
            let locked_mem = GlobalLock(hmem);
            if !locked_mem.is_null() {
                let wide_str = locked_mem as *const u16;
                let len = lstrlen_w(wide_str);
                let mut text_output = vec![0u16; len as usize + 1];
                ptr::copy_nonoverlapping(wide_str, text_output.as_mut_ptr(), len as usize);
                GlobalUnlock(hmem);
                
                let text_string = String::from_utf16_lossy(&text_output[..len as usize]);
                let type_str = cx.string("text");
                result.set(&mut cx, "type", type_str)?;
                let data_str = cx.string(text_string);
                result.set(&mut cx, "data", data_str)?;
                CloseClipboard();
                return Ok(result);
            }
        }
        
        // Try to read bitmap - try CF_DIBV5 first, then CF_DIB, and finally CF_BITMAP
        let dibv5_data = GetClipboardData(winapi::um::winuser::CF_DIBV5);
        let dib_data = if !dibv5_data.is_null() {
            dibv5_data
        } else {
            GetClipboardData(winapi::um::winuser::CF_DIB)
        };
        
        let bitmap_data = if !dib_data.is_null() {
            dib_data
        } else {
            GetClipboardData(CF_BITMAP)
        };
        
        if !bitmap_data.is_null() {
            let hmem = bitmap_data as HGLOBAL;
            let locked_mem = GlobalLock(hmem);
            if !locked_mem.is_null() {
                let size = GlobalSize(hmem);
                let mut bitmap_output = vec![0u8; size];
                ptr::copy_nonoverlapping(locked_mem as *const u8, bitmap_output.as_mut_ptr(), size);
                GlobalUnlock(hmem);
                
                let type_str = cx.string("image");
                result.set(&mut cx, "type", type_str)?;
                let mut data_buffer = JsBuffer::new(&mut cx, size)?;
                let buffer_slice = data_buffer.as_mut_slice(&mut cx);
                buffer_slice.copy_from_slice(&bitmap_output);
                result.set(&mut cx, "data", data_buffer)?;
                CloseClipboard();
                return Ok(result);
            }
        }
        
        CloseClipboard();
        
        // Default fallback
        let type_str = cx.string("text");
        result.set(&mut cx, "type", type_str)?;
        let data_str = cx.string("");
        result.set(&mut cx, "data", data_str)?;
        Ok(result)
    }
}

fn write_clipboard(mut cx: FunctionContext) -> JsResult<JsValue> {
    let clipboard_type = cx.argument::<JsString>(0)?.value(&mut cx);
    let data = cx.argument::<JsValue>(1)?;

    unsafe {
        if OpenClipboard(ptr::null_mut()) == 0 {
            return cx.throw_error("Failed to open clipboard");
        }
        
        EmptyClipboard();
        
        match clipboard_type.as_str() {
            "text" => {
                let text_data = data.downcast_or_throw::<JsString, _>(&mut cx)?.value(&mut cx);
                let wide_data: Vec<u16> = text_data.encode_utf16().chain(std::iter::once(0)).collect();
                let hmem = GlobalAlloc(GMEM_MOVEABLE, wide_data.len() * 2);
                if hmem.is_null() {
                    CloseClipboard();
                    return cx.throw_error("Failed to allocate memory for clipboard text");
                }
                
                let locked_mem = GlobalLock(hmem);
                if locked_mem.is_null() {
                    CloseClipboard();
                    return cx.throw_error("Failed to lock memory for clipboard text");
                }
                
                ptr::copy_nonoverlapping(wide_data.as_ptr(), locked_mem as *mut u16, wide_data.len());
                GlobalUnlock(hmem);
                SetClipboardData(CF_UNICODETEXT, hmem as *mut winapi::ctypes::c_void);
            },
            "image" => {
                let buffer_data = data.downcast_or_throw::<JsBuffer, _>(&mut cx)?;
                let data_vec = {
                    let buffer_slice = buffer_data.as_slice(&mut cx);
                    buffer_slice.to_vec()
                };
                
                // Check if the image data is in a known format (PNG, JPEG, etc.)
                // and convert it to BMP format for clipboard compatibility
                let dib_data = match image::guess_format(&data_vec) {
                    Ok(ImageFormat::Png) | Ok(ImageFormat::Jpeg) | Ok(ImageFormat::Gif) | Ok(ImageFormat::Bmp) => {
                        // Load the image
                        match image::load_from_memory(&data_vec) {
                            Ok(img) => {
                                // Convert to BMP format for clipboard
                                let mut bmp_bytes: Vec<u8> = Vec::new();
                                let mut cursor = Cursor::new(&mut bmp_bytes);
                                match img.write_to(&mut cursor, ImageFormat::Bmp) {
                                    Ok(_) => {
                                        // Remove the BMP file header (first 14 bytes) as the clipboard expects only the DIB
                                        if bmp_bytes.len() > 14 {
                                            bmp_bytes[14..].to_vec()
                                        } else {
                                            data_vec // fallback to original data
                                        }
                                    },
                                    Err(_) => data_vec, // fallback to original data
                                }
                            },
                            Err(_) => data_vec, // fallback to original data
                        }
                    },
                    _ => data_vec, // Unknown format, use as is
                };
                
                let hmem = GlobalAlloc(GMEM_MOVEABLE, dib_data.len());
                if hmem.is_null() {
                    CloseClipboard();
                    return cx.throw_error("Failed to allocate memory for clipboard image");
                }
                
                let locked_mem = GlobalLock(hmem);
                if locked_mem.is_null() {
                    CloseClipboard();
                    return cx.throw_error("Failed to lock memory for clipboard image");
                }
                
                ptr::copy_nonoverlapping(dib_data.as_ptr(), locked_mem as *mut u8, dib_data.len());
                GlobalUnlock(hmem);
                SetClipboardData(CF_DIBV5, hmem as *mut winapi::ctypes::c_void);
            },
            _ => {
                CloseClipboard();
                return cx.throw_error("Invalid clipboard type. Must be 'text' or 'image'");
            }
        }
        
        CloseClipboard();
        Ok(cx.undefined().upcast())
    }
}

fn write_clipboard_text(mut cx: FunctionContext) -> JsResult<JsValue> {
    let text = cx.argument::<JsString>(0)?.value(&mut cx);
    
    unsafe {
        if OpenClipboard(ptr::null_mut()) == 0 {
            return cx.throw_error("Failed to open clipboard");
        }
        
        EmptyClipboard();
        
        let wide_data: Vec<u16> = text.encode_utf16().chain(std::iter::once(0)).collect();
        let hmem = GlobalAlloc(GMEM_MOVEABLE, wide_data.len() * 2);
        if hmem.is_null() {
            CloseClipboard();
            return cx.throw_error("Failed to allocate memory for clipboard text");
        }
        
        let locked_mem = GlobalLock(hmem);
        if locked_mem.is_null() {
            CloseClipboard();
            return cx.throw_error("Failed to lock memory for clipboard text");
        }
        
        ptr::copy_nonoverlapping(wide_data.as_ptr(), locked_mem as *mut u16, wide_data.len());
        GlobalUnlock(hmem);
        SetClipboardData(CF_UNICODETEXT, hmem as *mut winapi::ctypes::c_void);
        
        CloseClipboard();
        Ok(cx.undefined().upcast())
    }
}

fn read_clipboard_text(mut cx: FunctionContext) -> JsResult<JsString> {
    unsafe {
        if OpenClipboard(ptr::null_mut()) == 0 {
            return cx.throw_error("Failed to open clipboard");
        }
        
        let unicode_data = GetClipboardData(CF_UNICODETEXT);
        if unicode_data.is_null() {
            CloseClipboard();
            return cx.throw_error("No unicode text in clipboard");
        }
        
        let hmem = unicode_data as HGLOBAL;
        let locked_mem = GlobalLock(hmem);
        if locked_mem.is_null() {
            CloseClipboard();
            return cx.throw_error("Failed to lock clipboard memory");
        }
        
        let wide_str = locked_mem as *const u16;
        let len = lstrlen_w(wide_str);
        let mut text_output = vec![0u16; len as usize + 1];
        ptr::copy_nonoverlapping(wide_str, text_output.as_mut_ptr(), len as usize);
        GlobalUnlock(hmem);
        CloseClipboard();
        
        let text_string = String::from_utf16_lossy(&text_output[..len as usize]);
        Ok(cx.string(text_string))
    }
}

fn read_clipboard_html(mut cx: FunctionContext) -> JsResult<JsString> {
    unsafe {
        if OpenClipboard(ptr::null_mut()) == 0 {
            return cx.throw_error("Failed to open clipboard");
        }
        
        // Register the HTML format
        let html_format_name: Vec<u16> = "HTML Format\0".encode_utf16().collect();
        let html_format = winapi::um::winuser::RegisterClipboardFormatW(html_format_name.as_ptr());
        
        // Try to read HTML format from clipboard
        if html_format != 0 {
            let html_data = GetClipboardData(html_format);
            if !html_data.is_null() {
                let hmem = html_data as HGLOBAL;
                let locked_mem = GlobalLock(hmem);
                if !locked_mem.is_null() {
                    let size = GlobalSize(hmem);
                    if size > 0 {
                        let html_output = std::slice::from_raw_parts(locked_mem as *const u8, size);
                        // Try to parse as UTF-8 first
                        if let Ok(html_string) = String::from_utf8(html_output.to_vec()) {
                            GlobalUnlock(hmem);
                            CloseClipboard();
                            return Ok(cx.string(html_string));
                        }
                        // Fallback to UTF-8 lossy conversion
                        let html_string = String::from_utf8_lossy(html_output).to_string();
                        GlobalUnlock(hmem);
                        CloseClipboard();
                        return Ok(cx.string(html_string));
                    }
                    GlobalUnlock(hmem);
                }
            }
        }
        
        // Fallback to plain text
        let unicode_data = GetClipboardData(CF_UNICODETEXT);
        if !unicode_data.is_null() {
            let hmem = unicode_data as HGLOBAL;
            let locked_mem = GlobalLock(hmem);
            if !locked_mem.is_null() {
                let wide_str = locked_mem as *const u16;
                let len = lstrlen_w(wide_str);
                let mut text_output = vec![0u16; len as usize + 1];
                ptr::copy_nonoverlapping(wide_str, text_output.as_mut_ptr(), len as usize);
                GlobalUnlock(hmem);
                CloseClipboard();
                
                let text_string = String::from_utf16_lossy(&text_output[..len as usize]);
                return Ok(cx.string(text_string));
            }
        }
        
        CloseClipboard();
        Ok(cx.string(""))
    }
}

fn read_clipboard_image(mut cx: FunctionContext) -> JsResult<JsBuffer> {
    unsafe {
        if OpenClipboard(ptr::null_mut()) == 0 {
            return cx.throw_error("Failed to open clipboard");
        }
        
        // Try CF_DIBV5 first
        let dibv5_data = GetClipboardData(winapi::um::winuser::CF_DIBV5);
        let is_dibv5 = !dibv5_data.is_null();
        let dib_data = if is_dibv5 {
            dibv5_data
        } else {
            // Fallback to CF_DIB
            GetClipboardData(winapi::um::winuser::CF_DIB)
        };
        
        if dib_data.is_null() {
            // 如果剪贴板中没有位图数据，返回一个空的缓冲区
            CloseClipboard();
            let buffer = JsBuffer::new(&mut cx, 0)?;
            return Ok(buffer);
        }
        
        let hmem = dib_data as HGLOBAL;
        if hmem.is_null() {
            CloseClipboard();
            let buffer = JsBuffer::new(&mut cx, 0)?;
            return Ok(buffer);
        }
        
        let locked_mem = GlobalLock(hmem);
        if locked_mem.is_null() {
            CloseClipboard();
            let buffer = JsBuffer::new(&mut cx, 0)?;
            return Ok(buffer);
        }
        
        let size = GlobalSize(hmem);
        if size == 0 {
            GlobalUnlock(hmem);
            CloseClipboard();
            let buffer = JsBuffer::new(&mut cx, 0)?;
            return Ok(buffer);
        }
        
        // Copy DIB data
        let dib_slice = slice::from_raw_parts(locked_mem as *const u8, size);
        let dib_buffer = dib_slice.to_vec();
        GlobalUnlock(hmem);
        CloseClipboard();
        
        // Convert DIB to BMP format
        let file_header_size = 14;
        if dib_buffer.len() < 4 {
            let buffer = JsBuffer::new(&mut cx, 0)?;
            return Ok(buffer);
        }
        
        // Read the size of the bitmap info header
        let actual_header_size = u32::from_le_bytes([
            dib_buffer[0], 
            dib_buffer[1], 
            dib_buffer[2], 
            dib_buffer[3]
        ]);
        
        // Calculate data offset
        let mut data_offset = file_header_size + actual_header_size as usize;
        if is_dibv5 && actual_header_size >= 52 {
            if dib_buffer.len() >= 20 {
                let _bit_count = u16::from_le_bytes([
                    dib_buffer[14],
                    dib_buffer[15]
                ]);
                
                let compression = u32::from_le_bytes([
                    dib_buffer[16],
                    dib_buffer[17],
                    dib_buffer[18],
                    dib_buffer[19]
                ]);
                
                // If compression is BI_BITFIELDS (3), there's an additional 12 bytes of mask data
                if compression == 3 {
                    data_offset += 12;
                }
            }
        }
        
        // Create full bitmap with file header
        let mut full_bitmap = vec![0u8; file_header_size + dib_buffer.len()];
        
        // Write BITMAPFILEHEADER
        full_bitmap[0] = b'B';
        full_bitmap[1] = b'M';
        // File size
        let file_size = (file_header_size + dib_buffer.len()) as u32;
        full_bitmap[2..6].copy_from_slice(&file_size.to_le_bytes());
        // Reserved fields (4-9) are 0
        // Data offset
        let data_offset_u32 = data_offset as u32;
        full_bitmap[10..14].copy_from_slice(&data_offset_u32.to_le_bytes());
        
        // Copy DIB data
        full_bitmap[file_header_size..].copy_from_slice(&dib_buffer);
        
        // Convert BMP to PNG
        match image::load_from_memory_with_format(&full_bitmap, ImageFormat::Bmp) {
            Ok(img) => {
                let mut png_data: Vec<u8> = Vec::new();
                let mut cursor = Cursor::new(&mut png_data);
                match img.write_to(&mut cursor, ImageFormat::Png) {
                    Ok(_) => {
                        let mut buffer = JsBuffer::new(&mut cx, png_data.len())?;
                        let buffer_slice = buffer.as_mut_slice(&mut cx);
                        buffer_slice.copy_from_slice(&png_data);
                        Ok(buffer)
                    }
                    Err(_) => {
                        // If PNG conversion fails, return the BMP data
                        let mut buffer = JsBuffer::new(&mut cx, full_bitmap.len())?;
                        let buffer_slice = buffer.as_mut_slice(&mut cx);
                        buffer_slice.copy_from_slice(&full_bitmap);
                        Ok(buffer)
                    }
                }
            }
            Err(_) => {
                // If BMP loading fails, return the raw data
                let mut buffer = JsBuffer::new(&mut cx, full_bitmap.len())?;
                let buffer_slice = buffer.as_mut_slice(&mut cx);
                buffer_slice.copy_from_slice(&full_bitmap);
                Ok(buffer)
            }
        }
    }
}

unsafe fn lstrlen_w(ptr: *const u16) -> i32 {
    let mut len = 0;
    while unsafe { *ptr.offset(len) } != 0 {
        len += 1;
    }
    len as i32
}

#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
    cx.export_function("readClipboard", read_clipboard)?;
    cx.export_function("writeClipboard", write_clipboard)?;
    cx.export_function("writeClipboardText", write_clipboard_text)?;
    cx.export_function("readClipboardText", read_clipboard_text)?;
    cx.export_function("readClipboardHtml", read_clipboard_html)?;
    cx.export_function("readClipboardImage", read_clipboard_image)?;
    Ok(())
}